package Script;

import Calculate.ByteCalc;

public class Offset implements Comparable<Object> {
	private Long value;
	private boolean endian; // True = big, false = little
	
	public Offset(long offset) {
		value = new Long(offset);
		endian = true;
	}

	
	/**
	 * Constructs the offset form a byte array.
	 * @param offsetArray Offset data in bytes.
	 */
	public Offset(byte[] offsetArray) {
		value = new Long(ByteCalc.byteArrayToLong(offsetArray));
		endian = true;
	}
	
	/**
	 * Returns the value of this offset as a hexadecimal string.
	 */
	public String getHex(int bytes) {
		String temp = Long.toHexString(getLongValue(bytes));
		
		if ((temp.length() % 2) != 0) {
			temp = "0" + temp;
		}
		return temp.toUpperCase();
	}
	
	/**
	 * Returns offset value as a long.
	 */
	public long getValue() {
		return value.longValue();
	}
	
	/**
	 * Returns the n lowest bytes of this offset.
	 */
	public long getLongValue(int n) {
		
		/* Convert from long to byte array */
		byte[] b = ByteCalc.longToByteArray(value);
		
		/* 
		 * Trim array, depending on if the bytes
		 * are at the front of the array (little endian)
		 * or at the back (big) 
		 */
		if (endian) {
			b = ByteCalc.subArray(b, b.length - n, b.length);
		} else {
			b = ByteCalc.subArray(b, 0, n);
		}
		
		/* Convert back. */
		return ByteCalc.byteArrayToLong(b);
	}
	
	/**
	 * Returns the n lowest bytes of this offset.
	 */
	public byte[] getByteValue(int n) {
		
		/* Convert from long to byte array */
		byte[] b = ByteCalc.longToByteArray(getLongValue(n));
		
		/* Trim array */
		b = ByteCalc.subArray(b, b.length - n, b.length);
		
		return b;
	}
	
	/**
	 * Returns the size of the offset, in number of bytes.
	 */
	public int byteSize() {
		return (int) Math.ceil(Long.toHexString(value).length() / 2.0);
	}
	
	/**
	 * Switches endianess on a bytearray.
	 * A maximum of 8 bytes.
	 */
	public void switchEndian() {
//		byte[] data;
//		int n = 0;
//		
//		/* Convert offset to a byte array. */
//		data = ByteCalc.longToByteArray(value.longValue());
//
//		
//		/* 
//		 * If there are preceeding 0's in the array,
//		 * they are moved to the back.
//		 */
//		while (data[0] == 0 && value != 0) {
//			for (int i = 1; i < data.length; i++) {
//				if (data[i] != 0) {
//					data[i - 1] = data[i];
//					data[i] = 0;
//				}
//			}
//			n++;
//		}
//		
//		/* Switch endianess. */
//		ByteBuffer buf = ByteBuffer.allocate(data.length);
//		buf = ByteBuffer.wrap(data);
//		buf.order(ByteOrder.LITTLE_ENDIAN);
//		
//		/* Store value. */
//		value = buf.getLong(0);
//		
//		setEndian(!endian);
		
		value = swap(value);
	}
	
	/**
	 * Wether this offset is big or little endian.
	 */
	public boolean getEndian() {
		return endian;
	}
	
	/**
	 * Sets the endian encoding of this offset.
	 * @param set True if big endian, false if little.
	 */
	public void setEndian(boolean set) {
		endian = set;
	}

	@Override
	public int compareTo(Object c) {
		Offset comp = (Offset) c;
		
		if (value < comp.getValue()) {
			return -1;
		} else if (value == comp.getValue()) {
			return 0;
		} else {
			return 1;
		}
	}
	
	public String toString() {
		return Long.toHexString(value);
	}
	
	/**
	 * Shifts between big and little endian.
	 * All 0's are left regardless, and must
	 * be removed manually when reading
	 * numbers with lower than 8 bytes.
	 * @param value Value to byte-swap.
	 * @return Value of different endianness.
	 */
	private long swap (long value) {
		long b1 = (value >>  0) & 0xff;
		long b2 = (value >>  8) & 0xff;
		long b3 = (value >> 16) & 0xff;
		long b4 = (value >> 24) & 0xff;
		long b5 = (value >> 32) & 0xff;
		long b6 = (value >> 40) & 0xff;
		long b7 = (value >> 48) & 0xff;
		long b8 = (value >> 56) & 0xff;
	
		value =  b1 << 56 | b2 << 48 | b3 << 40 | b4 << 32 |
		           b5 << 24 | b6 << 16 | b7 <<  8 | b8 <<  0;
		
		
		setEndian(!endian);
		return value;
	}

}
